package com.jgp.security.jpa.filters;

import com.jgp.common.annotation.DataPower;
import com.jgp.common.annotation.PowerForType;
import com.jgp.common.persistence.Model;
import com.jgp.security.config.SecUserAdapter;
import com.jgp.security.pojo.UserInfo;
import com.jgp.security.secmodel.SecGivePower;
import com.jgp.security.secmodel.SecInstitution;
import com.jgp.security.secmodel.SecRole;
import com.jgp.security.service.SecGivePowerService;
import com.jgp.security.service.SecInstitutionService;
import com.jgp.security.service.SecPowerService;
import com.jgp.sys.query.jpa.DataFilter;
import com.jgp.sys.query.power.DataPowerConfig;
import com.jgp.sys.query.power.DataPowerOperator;
import com.jgp.sys.utils.SpringContextUtil;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 项目   jgp-cloud-parent
 * 作者   loufei
 * 时间   2019-11-10
 */
public class DataPermissionFilter implements DataFilter {
	
	@Override
	public Predicate doPredicate(Root<? extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder, DataPowerConfig powerConfig) {
		List<Predicate> predicates = new ArrayList<>();
		if (!powerConfig.isEnabled()) {
			return null;
		}
		SecUserAdapter userAdapter = SpringContextUtil.getBean(SecUserAdapter.class);
		if (Objects.isNull(userAdapter)) {
			return null;
		}
		
		UserInfo userInfo = userAdapter.currentUser();
		List<SecInstitution> institutions = userInfo.getInstitutions();
		List<Long> institutionIds = institutions.stream().map(SecInstitution::getId).collect(Collectors.toList());
		
		SecGivePowerService powerService = SpringContextUtil.getBean(SecGivePowerService.class);
		List<SecGivePower> institutionPowers = powerService.queryAuthorizationWithInstitution(userInfo.getId());
		List<Long> institutionPowerIds = institutionPowers.stream()
														  .map(secGivePower -> Long.parseLong(secGivePower.getProvider()))
														  .collect(Collectors.toList());
		
		if(institutionPowerIds.size()>0){
			SecInstitutionService institutionService = SpringContextUtil.getBean(SecInstitutionService.class);
			Map<Long,SecInstitution> institutionMap = institutionService.queryFullMap(userInfo.getClientId());
			
			institutionPowerIds.forEach(id->{
				if(!institutionIds.contains(id+"") && !institutionIds.contains(id)){
					SecInstitution institution = institutionMap.get(id+"");
					if(Objects.isNull(institution)){
						institution = institutionMap.get(id);
					}
					if(Objects.nonNull(institution)){
						institutions.add(institution);
					}
				}
			});
		}
		
		List<SecRole> roles = userInfo.getRoles();
		
		if (Objects.nonNull(powerConfig) && powerConfig.getGroups().size()>0) {
			List<DataPowerConfig.Group> groups = powerConfig.getGroups();
			List<Predicate> psx = new ArrayList<>();
			groups.forEach(group -> {
				DataPowerOperator operator = group.getOperator();
				List<Predicate> ps = new ArrayList<>();
				group.getItems().forEach(item -> {
					Predicate predicate = predicate(item.getType(), item.getCode(), userInfo, institutions, roles, root, query, builder);
					if (Objects.nonNull(predicate)) ps.add(predicate);
				});
				if (ps.size() > 0) {
					if (DataPowerOperator.AND.equals(operator)) {
						psx.add(builder.and(ps.toArray(new Predicate[ps.size()])));
					} else {
						psx.add(builder.or(ps.toArray(new Predicate[ps.size()])));
					}
				}
				
			});
			return psx.size() > 0 ? builder.and(psx.toArray(new Predicate[psx.size()])) : null;
		} else {
			DataPower[] powers = root.getJavaType().getAnnotationsByType(DataPower.class);
			if (Objects.isNull(powers) || powers.length == 0) {
				return null;
			}
			
			List<DataPower> ps = Arrays.asList(powers).stream().distinct().collect(Collectors.toList());
			
			for (DataPower power : ps) {
				Predicate predicate = predicate(power.value(), null, userInfo, institutions, roles, root, query, builder);
				if (Objects.nonNull(predicate)) predicates.add(predicate);
			}
			
			predicates = predicates.stream().filter(Objects::nonNull).collect(Collectors.toList());
		}
		return predicates.size() > 0 ? builder.or(predicates.toArray(new Predicate[predicates.size()])) : null;
	}
	
	private Predicate predicate(
			PowerForType type, String code, UserInfo userInfo, List<SecInstitution> institutions, List<SecRole> roles, Root<? extends Model> root,
            CriteriaQuery<?> query, CriteriaBuilder builder) {
		if (PowerForType.CUSTOM.equals(type)) {
			return customCondition(type, userInfo.getId(), institutions, roles, root, query, builder);
		}
		if (PowerForType.SPECIAL.equals(type)) {
			Map<String, List<SecInstitution>> map = userInfo.getSInsAllMap();
			List<SecInstitution> secInstitutions = map.get(code);
			return specialCondition(secInstitutions, root, query, builder);
		} else {
			return commonCondition(type, userInfo.getId(), institutions, roles, root, query, builder);
		}
	}
	
	private Predicate customCondition(
			PowerForType type, String userId, List<SecInstitution> institutions, List<SecRole> roles, Root<? extends Model> root,
            CriteriaQuery<?> query, CriteriaBuilder builder) {
		return null;
	}
	
	private Predicate specialCondition(List<SecInstitution> secInstitutions, Root<? extends Model> root, CriteriaQuery<?> query,
                                       CriteriaBuilder builder) {
		Predicate predicate = null;
		if (Objects.nonNull(secInstitutions) && secInstitutions.size() > 0) {
			predicate = specialInstitution(root, query, secInstitutions);
		}
		return predicate;
	}
	
	private Predicate specialInstitution(Root<? extends Model> root, CriteriaQuery<?> query, List<SecInstitution> secInstitutions) {
		SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
		List<String> rowIds = powerService.queryRowIdsBySpecialInstitutionsAndDown(secInstitutions);
		if (rowIds.size() > 0) {
			return root.get("rowId").in(rowIds);
		} else {
			return null;
		}
		
	}
	
	private Predicate commonCondition(
			PowerForType type, String userId, List<SecInstitution> institutions, List<SecRole> roles, Root<? extends Model> root,
            CriteriaQuery<?> query, CriteriaBuilder builder) {
		Predicate predicate = null;
		switch (type) {
			case ORG:
				if (institutions.size() > 0) {
					predicate = sameInstitution(root, query, institutions);
				}
				break;
			case ORG_DOWN:
				if (institutions.size() > 0) {
					predicate = institutionDown(root, query, builder, institutions);
				}
				break;
			case ROLE:
				if (roles.size() > 0) {
					predicate = sameRole(root, query, roles);
				}
				break;
			case ROLE_DOWN:
				if (roles.size() > 0) {
					predicate = roleDown(root, query, builder, roles);
				}
				break;
			case ORG_ROLE:
				if (roles.size() > 0 && institutions.size() > 0) {
					Predicate p0 = sameRole(root, query, roles);
					Predicate p1 = sameInstitution(root, query, institutions);
					predicate = builder.and(p0, p1);
					break;
				}
			case PRIVATE:
				predicate = builder.equal(root.get("rowId"), userId);
				break;
			case CUSTOM:
				break;
			
		}
		return predicate;
	}
	
	private Predicate sameInstitution(Root root, CriteriaQuery<?> query, List<SecInstitution> institutions) {
        /*List<Long> insIds = institutions.stream().map(SecInstitution::getId).collect(Collectors.toList());
        Subquery insUserQuery = query.subquery(String.class);
        Root insUserRoot = insUserQuery.from(SecInstitutionUser.class);
        insUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("institutionId").in(insIds));*/
		SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
		return root.get("rowId").in(powerService.queryRowIdsByInstitutions(institutions));
	}
	
	
	private Predicate sameRole(Root root, CriteriaQuery<?> query, List<SecRole> roles) {
		
		SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
		List<String> rowIds = powerService.queryRowIdsByRoles(roles);
        /*  List<Long> roleIds = roles.stream().map(SecRole::getId).collect(Collectors.toList());
        Subquery roleUserQuery = query.subquery(String.class);
        Root insUserRoot = roleUserQuery.from(SecRoleUser.class);
        roleUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("roleId").in(roleIds));*/
		return root.get("rowId").in(rowIds);
	}
	
	private Predicate institutionDown(Root root, CriteriaQuery<?> query, CriteriaBuilder builder, List<SecInstitution> institutions) {
		
		SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
		List<String> rowIds = powerService.queryRowIdsByInstitutionsAndDown(institutions);
        
        /*Subquery insUserQuery = query.subquery(String.class);
        Root insUserRoot = insUserQuery.from(SecInstitutionUser.class);
        
        Subquery insQuery = insUserQuery.subquery(SecInstitution.class);
        Root insRoot = insQuery.from(SecInstitution.class);
        List<Predicate> predicates = new ArrayList<>();
        for (SecInstitution ins : institutions) {
            predicates.add(builder.like(insRoot.get("supers"), ins.getSupers() + "%"));
        }
        Predicate where = builder.or(predicates.toArray(new Predicate[predicates.size()]));
        Subquery subquery = insQuery.select(insRoot.get("id")).where(where);
        insUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("institutionId").in(subquery));*/
		
		return root.get("rowId").in(rowIds);
	}
	
	private Predicate roleDown(Root root, CriteriaQuery<?> query, CriteriaBuilder builder, List<SecRole> roles) {
        
        /*Subquery roleUserQuery = query.subquery(String.class);
        Root roleUserRoot = roleUserQuery.from(SecRoleUser.class);
        
        Subquery insQuery = roleUserQuery.subquery(SecInstitution.class);
        Root roleRoot = insQuery.from(SecRole.class);
        List<Predicate> predicates = new ArrayList<>();
        for (SecRole role : roles) {
            predicates.add(builder.like(roleRoot.get("supers"), role.getSupers() + "%"));
        }
        Predicate where = builder.or(predicates.toArray(new Predicate[predicates.size()]));
        Subquery subquery = insQuery.select(roleRoot.get("id")).where(where);
        roleUserQuery.select(roleUserRoot.get("userId")).where(roleUserRoot.get("roleId").in(subquery));*/
		SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
		List<String> rowIds = powerService.queryRowIdsByRolesAndDown(roles);
		return root.get("rowId").in(rowIds);
	}
}
